<template>
    <div>
        <h2>watch 功能详解</h2>
        <p>count: {{ count }}</p>
        <button @click="addCount">增加</button>
    </div>
    <div>
        <h2>watch 侦听 ref 对象类型数据</h2>
        <p>name: {{ person.name }}</p>
        <p>age: {{ person.age }}</p>
        <button @click="changeName">修改姓名</button>
        <button @click="changeAge">修改年龄</button>
        <button @click="changePerson">修改 person</button>
    </div>
    <div>
        <h2>watch 侦听 reactive 对象</h2>
        <p>name: {{ user.name }}</p>
        <p>age: {{ user.age }}</p>
        <button @click="changeUserName">修改姓名</button>
        <button @click="changeUserAge">修改年龄</button>
        <button @click="changeUser">修改 user</button>
    </div>
    <div>
        <h2>watch 侦听 reactive 对象的某个属性</h2>
        <p>name: {{ persionDetail.name }}</p>
        <p>age: {{ persionDetail.age }}</p>
        <p>province: {{ persionDetail.address.province }}</p>
        <p>city: {{ persionDetail.address.city }}</p>
        <ul>
            <li>name: {{ persionDetail.char.charName }}</li>
            <li>spec: {{ persionDetail.char.charSpec }}</li>
        </ul>
        <button @click="changePersionDetailName">修改姓名</button>
        <button @click="changePersionDetailAddress">修改地址</button>
        <button @click="changeCharSpec">修改规格</button>
        </div>  
</template>
<script lang="ts" setup>
/**
 * Vue 中的 watch 功能详解
 * 
 * 1. 基本概念：
 *    watch 是 Vue 提供的一个用于侦听响应式数据变化并执行相应回调函数的 API。
 *    当被侦听的数据发生变化时，会触发指定的回调函数。
 * 
 * 2. 与计算属性的区别：
 *    - 计算属性：基于依赖进行缓存，适合进行数据转换
 *    - watch：更适合在数据变化时执行异步操作或开销较大的操作
 * 
 * 3. 使用场景：
 *    - 当需要在数据变化时执行异步操作（如发送 API 请求）
 *    - 当需要在数据变化时执行较复杂的逻辑
 *    - 当需要监听多个数据源的变化
 *    - 当需要限制操作执行的频率（如防抖、节流）
 *    - 当需要在数据变化时访问变化前后的值
 * 
 * 4. 基本语法：
 *    watch(source, callback, [options])
 *    - source: 要侦听的响应式引用或 getter 函数
 *    - callback: 数据变化时调用的回调函数
 *    - options: 可选的配置对象
 * 
 * 5. 常用写法示例：
 * 
 *    // 5.1 侦听 ref
 *    const count = ref(0)
 *    watch(count, (newValue, oldValue) => {
 *      console.log(`count 从 ${oldValue} 变为 ${newValue}`)
 *    })
 * 
 *    // 5.2 侦听 reactive 对象的属性 - 需要使用 getter 函数
 *    const user = reactive({ name: '张三', age: 25 })
 *    watch(
 *      () => user.name,
 *      (newName, oldName) => {
 *        console.log(`name 从 ${oldName} 变为 ${newName}`)
 *      }
 *    )
 * 
 *    // 5.3 同时侦听多个数据源
 *    watch(
 *      [() => user.name, () => user.age],
 *      ([newName, newAge], [oldName, oldAge]) => {
 *        console.log(`name: ${oldName} -> ${newName}, age: ${oldAge} -> ${newAge}`)
 *      }
 *    )
 * 
 *    // 5.4 侦听整个 reactive 对象
 *    // 注意：这种方式无法获取到旧值，因为对象引用没变
 *    watch(user, (newValue, oldValue) => {
 *      // 这里 newValue 和 oldValue 是相同的引用
 *      console.log('user 对象变化了')
 *    }, { deep: true }) // 需要设置 deep: true
 * 
 *    // 5.5 使用 watchEffect - 自动追踪依赖
 *    // 不需要明确指定要侦听的数据，会自动追踪回调中使用的响应式数据
 *    watchEffect(() => {
 *      console.log(`name: ${user.name}, age: ${user.age}`)
 *      // 这个回调会立即执行一次，之后当 user.name 或 user.age 变化时再次执行
 *    })
 * 
 * 6. 常用配置选项：
 *    - deep: boolean - 是否深度侦听对象内部值的变化
 *    - immediate: boolean - 是否在侦听器创建时立即触发回调
 *    - flush: 'pre' | 'post' | 'sync' - 控制回调的刷新时机
 *      - 'pre': 组件更新前调用（默认）
 *      - 'post': 组件更新后调用
 *      - 'sync': 同步调用
 *    - onTrack / onTrigger: 用于调试的钩子函数
 * 
 * 7. 停止侦听：
 *    const stopWatch = watch(source, callback)
 *    // 当需要停止侦听时：
 *    stopWatch()
 * 
 * 8. 注意事项：
 *    - 在 setup() 或 <script setup> 中，watch 会在组件卸载时自动停止
 *    - 侦听 reactive 对象时，oldValue 可能与 newValue 相同，因为它们是同一引用
 *    - 使用 deep 选项可能会影响性能，特别是对于大型对象
 *    - 如果只需要执行副作用而不关心具体变化，可以使用 watchEffect
 */

import { ref, reactive, watch, watchEffect } from 'vue'

const count = ref(0)

function addCount() {
    count.value++
}
// 情况一：watch 侦听 ref
// 参数1：要侦听的响应式引用或 getter 函数
// 参数2：数据变化时调用的回调函数
const result = watch(count, (newValue, oldValue) => {
    console.log(`count 从 ${oldValue} 变为 ${newValue}`)  
    if (count.value === 5) {
        result(); 
    }
})
// 情况二：watch 侦听 ref 对象类型数据
const person = ref({name: '张三', age: 25})
function changeName() {
    person.value.name += '~' 
}
function changeAge() {
    person.value.age += 1
}
function changePerson() {
    person.value = {name: '李四', age: 30} 
}
// 监听整个 ref 定义的对象类型数据，监听的是对象的引用(地址值)
// 当 person 变化时，会触发回调函数
watch(person, (newValue, oldValue) => {
    console.log('person 变化了', newValue, oldValue)
})
// 监听 ref 对象类型数据的某个属性, 监听的是属性的值,开启深度监听
// 当 person.name 变化时，会触发回调函数
// 开启深度监听，会监听对象内部值的变化
// deep: true - 是否深度侦听对象内部值的变化
// immediate: true - 是否在侦听器创建时立即触发回调
// 
watch(person, (newValue, oldValue) => {
    console.log('person 变化了', newValue, oldValue)
}, {deep: true,immediate: true})
// watch(() => person.value.name, (newName, oldName) => {
//     console.log(`name 从 ${oldName} 变为 ${newName}`) 
// })

// 情况三：watch 侦听 reactive 对象
// watch 侦听 reactive 对象数据是默认开启深度监听的，对象属性变化时，会触发回调函数，并且无法关闭深度监听
// 当 监听的是 reactive 对象时，oldValue 可能与 newValue 相同，因为它们是同一引用
// 参数1：要侦听的响应式引用或 getter 函数
// 参数2：数据变化时调用的回调函数
const user = reactive({ name: '张三', age: 25 })

function changeUserName() {
    user.name += '~'
}
function changeUserAge() {
    user.age += 1 
}

function changeUser() {
   user.name = '李四'
   user.age = 30
//    user = {name: '王五', age: 35} // 会报错，因为 user 是 reactive 对象，不能直接赋值，只能修改属性
}

watch(user, (newValue, oldValue) => {
    console.log('user 变化了', newValue, oldValue)
})

const persionDetail = reactive({
    name: '张三',
    age: 25,
    address: {
        province: '广东省',
        city: '深圳市'
    },
    char: {
        charName : '宝马',
        charSpec : 'X7'
    }
})

function changePersionDetailName() {
    persionDetail.name += '~' 
}

function changePersionDetailAddress() {
    persionDetail.address.city += '~'
}

function changeCharSpec() {
    persionDetail.char.charSpec += '~' 
}

// 情况四：watch 监听 ref 或者 reactive 对象的某个属性 写法
// 当 监听的是 reactive 对象的某个属性时，oldValue 可能与 newValue 相同，因为它们是同一引用
// 如果要监听对象的某个属性，需要使用 getter 函数，写法为 () => persionDetail.name
// 参数1：要侦听的响应式引用或 getter 函数
// 参数2：数据变化时调用的回调函数
watch(() => persionDetail.name, (newName, oldName) => {
    console.log(`name 从 ${oldName} 变为 ${newName}`) 
})

watch(() => persionDetail.address.city, (newCity, oldCity) => {
    console.log(`city 从 ${oldCity} 变为 ${newCity}`)
})
// persionDetail.char 监听的是对象的引用(地址值) ，但是无法监听对象所有属性的变化
watch(()=>persionDetail.char, (newValue, oldValue) => {
    console.log('persionDetail Char 变化了', newValue, oldValue)
})

// 情况五：监听多个数据源
// 参数1：要侦听的响应式引用或 getter 函数

watch(
    [() => persionDetail.name, () => persionDetail.address.city, () => persionDetail.char.charSpec],
    ([newName, newCity,newSpec], [oldName, oldCity,oldSpec]) => {
        console.log(`name: ${oldName} -> ${newName}, city: ${oldCity} -> ${newCity}, spec: ${oldSpec} -> ${newSpec}`)
    } 
) 

</script>
<style scoped>
</style>